package com.atguigu.spzx.common.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.spyx.model.common.ResultCodeEnum;
import com.atguigu.spyx.model.common.com.atguigu.spyx.model.entity.sys.SysOperLog;
import com.atguigu.spyx.model.exception.SpzxException;
import com.atguigu.spzx.common.anno.OperLog;
import com.atguigu.spzx.common.service.SysOperLogService;
import com.atguigu.spzx.common.util.AuthUtils;
import com.atguigu.spzx.common.util.IpUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component
@Aspect
//指定切面类的优先级：值越小优先级越高
// 优先级一样的 系统的切面类优先级高(会先加载)
//如果我们自己的切面类将异常try catch了，没有抛出，系统的切面类无法获取异常，认为没有异常，事务aop不能回滚，异常处理器也不会生效
@Order(value = -1)
public class OperLogAspect {
    @Resource
    SysOperLogService sysOperLogService;
    //需要对目标方法执行后的结果进行判断 必须使用环绕通知
    //切入点表达式: 使用注解的全类名作为切入点
    @Around("@annotation(operLog)") //表示使用变量对应的注解类型作为切入点，并且将注解对象注入
    public Object around(ProceedingJoinPoint joinPoint, OperLog operLog){//joinPoint代表增强的目标方法的连接点对象 可以获取目标方法信息
        //生成并记录操作日志：
        SysOperLog sysOperLog = new SysOperLog();
        try {
            //获取请求对象
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String requestMethod = request.getMethod();
            String ipAddress = IpUtil.getIpAddress(request);
            String requestURI = request.getRequestURI();
            //获取目标方法对象
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取方法信息
            String methodName = signature.getMethod().getName();
            String clazzName = signature.getMethod().getDeclaringClass().getName();
            //1、获取目标方法的参数列表
            Object[] args = joinPoint.getArgs();

            //因为需要动态的获取注解标注的方法上的一些数据(可能每个都需要单独配置)
            /*
                注解中需要携带的参数：
                    1、title：模块标题
                    2、businessType:
                    3、operator_type：
                    4、isSaveRequestParams 是否需要保存请求参数
                    5、isSaveResponseData 是否需要保存响应结果
             */
            sysOperLog.setTitle(operLog.title());
            sysOperLog.setMethod(clazzName+" "+ methodName);
            sysOperLog.setRequestMethod(requestMethod);
            sysOperLog.setOperatorType(String.valueOf(operLog.operatorType()));
            sysOperLog.setBusinessType(operLog.businessType());
            sysOperLog.setOperName(AuthUtils.get().getName());
            sysOperLog.setOperUrl(requestURI);
            sysOperLog.setOperIp(ipAddress);//访问的管理员的电脑IP地址
            //请求参数
            if(operLog.isSaveRequestParams()){
                //保存请求参数: 课件中对请求参数遍历判断
                sysOperLog.setOperParam(JSON.toJSONString(args));
            }
            //执行目标方法得到结果后返回
            //2、执行目标方法
            Object result = joinPoint.proceed(args);

            if(operLog.isSaveResponseData()){
                //保存响应结果
                sysOperLog.setJsonResult(JSON.toJSONString(result));
            }
            sysOperLog.setStatus(0);
            //3、返回结果
            return result;
        } catch (Throwable e) {
            sysOperLog.setStatus(1);
            sysOperLog.setErrorMsg(e.getMessage());

            throw new SpzxException(ResultCodeEnum.ERROR, e);
        }finally {
            //保存正常或异常的操作日志
            /*
                common-service:
                    common-service所在的容器中是否有spzx-manager的组件对象：
                        - spzx-manager引入了common-service的依赖
                            spzx-manager启动类启动时初始化容器
                            可以扫描spzx-manager启动类可以扫描到的所有的组件类注入到容器中
                            也可以将springboot自动配置文件中的类扫描到注入到容器中
                            也可以将启动类上@Import或者@Enablexx注解 引入的组件类注入到容器中

                    为什么不能装配 spzx-manager中写的组件类
                        - common-service没有引入spzx-manager的依赖，也不能引入(循环依赖)
                        - common-service不能使用spzx-manager中编写的类
                    解决：
                        可以将common-service需要使用的组件类，定义到common-service中编写让spzx-manager扫描到它
                        spzx-manager也可以使用

             common-service需要使用spzx-manager中的Mapper保存日志
                解决：
                     common-service定义SysOperLogService接口
                      spzx-manager中创建实现类并添加组件注解
                      common-service中的组件类就可以使用 SysOperLogService接口自动装配实现类对象


             */
            sysOperLogService.save(sysOperLog);
        }

    }


}